/**
 * 
 */
package pacMan;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Arc2D;

/**
 * 
 * Class: PlayerObject
 * This is the player Actor object that is controlled by the user. Handles drawing itself at the
 * location on-screen specified by the Coordinate object, as well as animating itself as it moves
 * or in special cases such as when the player is 'killed' by an enemy Actor.
 *
 * @author 		Tomas Rivera
 * @version 	%$Id$
 * 
 */
public class PlayerObject extends Actor {
	
	private static final int STARTING_PLAYER_LIVES_PER_GAME = 3;

	//////////////////////////////////////////////////////////
	// Constructor methods
	//////////////////////////////////////////////////////////
	
	/**
	 * Creates a PlayerObject.
	 * 
	 * @param c
	 * @param w
	 * @param scoreTracker
	 * @param objectLocationTracker
	 */
	public PlayerObject(Coordinate c, WindowParameters w, ScoreTracker scoreTracker, ObjectLocationTracker objectLocationTracker) {
		super(c, w, 0, objectLocationTracker);
		// TODO Auto-generated constructor stub
		this.scoreTracker = scoreTracker;
		this.setHomeLocation(c);
		setDirection(CardinalDirection.EAST);
		newHorizontalDirectionRequest = CardinalDirection.HNONE;
		newVerticalDirectionRequest = CardinalDirection.VNONE;
		latestDirectionRequest = CardinalDirection.NONE;
		startAngle = 210;
		arcAngle = 300;
		pacMan = new Arc2D.Double(0, 0, 40, 40, startAngle, arcAngle, Arc2D.PIE);
		ghostsConsumedWhileEnergized = 0;
		mostRecentPointValue = 0;
		textFont = new Font("Lucida Console", Font.BOLD, 18);
		livesRemaining = STARTING_PLAYER_LIVES_PER_GAME;
		objectLocationTracker.registerPlayerObject(this);
	}
	
	//////////////////////////////////////////////////////////
	// Public methods
	//////////////////////////////////////////////////////////
	
	/**
	 * Increments the ScoreTracker player score by the amount pointValue. When 
	 * isEnemyActor is TRUE, the pointValue is multiplied by 2^n where n is the
	 * number of Ghosts that have been consumed since we most recently consumed
	 * an Energizer pellet.
	 * 
	 * @param pointValue
	 * @param isEnemyActor
	 */
	public void addToScore(int pointValue, boolean isEnemyActor) {
		if(isEnemyActor) { pointValue *= incrementGhostsConsumed(); }
		if(scoreTracker.addToScore(pointValue) == true) {
			incrementLivesRemaining();
		}
		mostRecentPointValue = pointValue;
	}
	
	/**
	 * Flags that the user has requested that the PlayerObject change its
	 * travel vector in the specified direction.
	 * 
	 * @param direction
	 */
	public void requestDirectionChange(CardinalDirection direction) {
		switch(direction) {
			case NORTH:
			case SOUTH:
				latestDirectionRequest = direction;
			case VNONE:
				newVerticalDirectionRequest = direction;
				//System.out.printf("Vertical Direction change request: %s%n", direction.toString());
				break;
				
			case EAST:
			case WEST:
				latestDirectionRequest = direction;
			case HNONE:
				newHorizontalDirectionRequest = direction;
				//System.out.printf("Horizontal Direction change request: %s%n", direction.toString());
				break;
				
			default:
				break;
		}
	}
	
	/**
	 * 
	 * @return
	 */
	public CardinalDirection getPlayerVerticalDirectionChange() { return newVerticalDirectionRequest; }
	
	/**
	 * 
	 * @return
	 */
	public CardinalDirection getPlayerHorizontalDirectionChange() { return newHorizontalDirectionRequest; }
	
	/**
	 * 
	 * @return
	 */
	public CardinalDirection getPlayerLastDirectionChange() { return latestDirectionRequest; }
	
	/**
	 * 
	 * @return
	 */
	public int incrementGhostsConsumed() {
		int multiplier = (int)Math.pow(2, ghostsConsumedWhileEnergized);
		System.out.printf("Ghosts consumed with this energizer: %d%n", ++ghostsConsumedWhileEnergized);
		return multiplier; 
	}
	
	/**
	 * 
	 */
	public void resetGhostsConsumed() {
		System.out.println("ghostsConsumedWhileEnergized = 0");
		ghostsConsumedWhileEnergized = 0; 
	}
	
	/**
	 * Get the number of lives remaining for the user.
	 * @return
	 */
	public int getLivesRemaining() { return livesRemaining; }
	
	/**
	 * Increment the number of lives remaining by 1.
	 */
	public void incrementLivesRemaining() { livesRemaining++; }
	
	/**
	 * Decrement the number of lives remaining by 1 and return that value.
	 * 
	 * @return
	 */
	public int decrementLivesRemaining() { return --livesRemaining; }
	
	/**
	 * Reset number of lives remaining back to the default.
	 */
	public void resetLivesRemaining() { livesRemaining = STARTING_PLAYER_LIVES_PER_GAME; }

	//////////////////////////////////////////////////////////
	// Overridden methods
	//////////////////////////////////////////////////////////
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.BasicDrawableObject#drawObject(java.awt.Graphics2D)
	 */
	@Override
	public void drawObject(Graphics2D g2d) {
		if(!visible()) return;
		

		g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        
        int actorSize = getWindowParams().getActorScale();
		int offsetX = getObjectLocation().getX() - (actorSize / 2);
		int offsetY = getObjectLocation().getY() - (actorSize / 2);
		
        if(hideObject()) {
        	g2d.setColor(Color.WHITE);
    		g2d.setFont(textFont);
    		g2d.drawString(Integer.toString(mostRecentPointValue), offsetX, offsetY);
        	return;
        }
        
        g2d.setColor(Color.YELLOW);
		double sAngle = 0, aAngle = 0;
		
		
        switch(getDirection()) {
			default:
				break;
			case NORTH:
				startAngle = 120;
				sAngle = ((double)((offsetY - getWindowParams().getPixelOffsetY()) % (actorSize / 2))) / ((double)(actorSize / 2)) * 30.0;
				if(((offsetY - getWindowParams().getPixelOffsetY()) % actorSize) >= (actorSize / 2)) {
					sAngle = 30.0 - sAngle;
				}
				
				aAngle =  sAngle * 2.0;
				break;
			case SOUTH:
				startAngle = 300;
				sAngle = ((double)((offsetY - getWindowParams().getPixelOffsetY()) % (actorSize / 2))) / ((double)(actorSize / 2)) * 30.0;
				if(((offsetY - getWindowParams().getPixelOffsetY()) % actorSize) >= (actorSize / 2)) {
					sAngle = 30.0 - sAngle;
				}
				
				aAngle =  sAngle * 2.0;
				break;
			case EAST:
				startAngle = 30;
				sAngle = ((double)((offsetX - getWindowParams().getPixelOffsetX()) % (actorSize / 2))) / ((double)(actorSize / 2)) * 30.0;
				if(((offsetX - getWindowParams().getPixelOffsetX()) % actorSize) >= (actorSize / 2)) {
					sAngle = 30.0 - sAngle;
				}
				
				aAngle =  sAngle * 2.0;
				break;
			case WEST:
				startAngle = 210;
				sAngle = ((double)((offsetX - getWindowParams().getPixelOffsetX()) % (actorSize / 2))) / ((double)(actorSize / 2)) * 30.0;
				if(((offsetX - getWindowParams().getPixelOffsetX()) % actorSize) >= (actorSize / 2)) {
					sAngle = 30.0 - sAngle;
				}
				
				aAngle =  sAngle * 2.0;
				break;
        }
        
        //System.out.printf("offsetX=%d, offsetY=%d sAngle=%02f%n", offsetX, offsetY, sAngle);
        
		pacMan.setArc(offsetX, offsetY, actorSize, actorSize, startAngle - sAngle, arcAngle + aAngle, Arc2D.PIE);
		g2d.fill(pacMan);
	}
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.BasicDrawableObject#resizeObject()
	 */
	@Override
	public void resizeObject() {
		System.out.printf("old position: x=%d y=%d%n", getObjectLocation().getX(), getObjectLocation().getY());
		System.out.printf("windowParams: width=%d, height=%d%n", this.getWindowParams().getWindowWidth(), this.getWindowParams().getWindowHeight());
		getObjectLocation().update(this.getWindowParams().getWindowWidth(), this.getWindowParams().getWindowHeight());
		System.out.printf("new position: x=%d y=%d%n", getObjectLocation().getX(), getObjectLocation().getY());
	}
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.ConsumableObject#consumeObject(pacMan.Actor)
	 */
	@Override
	public void consumeObject(Actor consumer) { 
		// Handle player killed
		decrementLivesRemaining();
	}
	
	/*
	 * (non-Javadoc)
	 * @see pacMan.Actor#moveActor(pacMan.GameStateManager.MOVE_BEHAVIOR)
	 */
	@Override
	public GameStateManager.GAME_EVENT moveActor(GameStateManager.MOVE_BEHAVIOR ghostGlobalState) {
		if(isCenteredHorizontally() && isCenteredVertically()) 
		{
			if(getPlayerHorizontalDirectionChange() != CardinalDirection.HNONE &&
				getPlayerVerticalDirectionChange() != CardinalDirection.VNONE)
			{
				if(getPlayerLastDirectionChange() == getPlayerHorizontalDirectionChange()) 
				{
					if(objectLocationTracker.legalMove(this, getPlayerHorizontalDirectionChange()))
					{
						setDirection(getPlayerHorizontalDirectionChange());
					}
					else if (objectLocationTracker.legalMove(this, getPlayerVerticalDirectionChange()))
					{
						setDirection(getPlayerVerticalDirectionChange());
					}
				} 
				else
				{
					if (objectLocationTracker.legalMove(this, getPlayerVerticalDirectionChange()))
					{
						setDirection(getPlayerVerticalDirectionChange());
					}
					else if(objectLocationTracker.legalMove(this, getPlayerHorizontalDirectionChange()))
					{
						setDirection(getPlayerHorizontalDirectionChange());
					}
				}
			}
			else if(getPlayerHorizontalDirectionChange() != CardinalDirection.HNONE)
			{
				if(objectLocationTracker.legalMove(this, getPlayerHorizontalDirectionChange()))
				{
					setDirection(getPlayerHorizontalDirectionChange());
				}
			}
			else if(getPlayerVerticalDirectionChange() != CardinalDirection.VNONE)
			{
				if (objectLocationTracker.legalMove(this, getPlayerVerticalDirectionChange()))
				{
					setDirection(getPlayerVerticalDirectionChange());
				}
			}
		}
			
		
		if(objectLocationTracker.legalMove(this, getDirection())) {
			moveActor(1);
			
			Ghost ghostAtTile = (Ghost) objectLocationTracker.checkForPlayerEnemyCollision();
			if(ghostAtTile != null && !ghostAtTile.wasEatenByPlayer()) {
				if(ghostAtTile.isScared()) {
					ghostAtTile.consumeObject(this);
					this.setHiddenWhileFrozen();
					return GameStateManager.GAME_EVENT.GHOST_CONSUMED;
				}
			}
			
			ConsumableObject consumed;
			if((consumed = objectLocationTracker.checkForStationaryConsumable(this)) != null) {
				if(consumed.getClass().getSimpleName().equals(Pellet.class.getSimpleName())) {
					return GameStateManager.GAME_EVENT.PELLET_CONSUMED;
				
				} else if(consumed.getClass().getSimpleName().equals(Energizer.class.getSimpleName())) {
					resetGhostsConsumed();
					return GameStateManager.GAME_EVENT.ENERGIZER_CONSUMED;
					
				} else if(consumed.getClass().getSimpleName().equals(BonusItem.class.getSimpleName())) {
					return GameStateManager.GAME_EVENT.BONUS_ITEM_CONSUMED;
				} else {
					assert false;
				}
			}
		}
		
		return GameStateManager.GAME_EVENT.NONE;
	}
	
	// TODO: Player death animation
	
	//////////////////////////////////////////////////////////
	// Private member variables
	//////////////////////////////////////////////////////////
	
	private ScoreTracker scoreTracker;
	private CardinalDirection newHorizontalDirectionRequest;
	private CardinalDirection newVerticalDirectionRequest;
	private CardinalDirection latestDirectionRequest;
	private int startAngle;
	private double arcAngle;
	private Arc2D pacMan;
	private int ghostsConsumedWhileEnergized;
	private int mostRecentPointValue;
	private Font textFont;
	private int livesRemaining;
}
